using System.IdentityModel.Tokens.Jwt;
using HamsterDocs.Domains.Docs;
using HamsterDocs.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace HamsterDocs.Hosting.Services;

public class DocsService
{
    [Authorize(Policy = "User")]
    [HttpPost("/projects")]
    public async Task<ActionResult<ProjectResponse>> CreateProject(
        [FromBody] CreateProjectRequest req,
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        if (req is null || string.IsNullOrWhiteSpace(req.Name))
        {
            return new BadRequestObjectResult(new { error = "InvalidRequest" });
        }

        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;
        var exists = await db.Set<DocsProject>()
            .AsNoTracking()
            .AnyAsync(x => x.UserId == userId && x.Name == req.Name);
        if (exists)
        {
            return new BadRequestObjectResult(new { error = "ProjectExists" });
        }

        var project = new DocsProject
        {
            Name = req.Name,
            Description = req.Description ?? string.Empty,
            Avatar = req.Avatar,
            UserId = userId,
            Public = req.Public
        };

        db.Set<DocsProject>().Add(project);
        await db.SaveChangesAsync();

        return new OkObjectResult(new ProjectResponse(project.Id, project.Name, project.Description ?? string.Empty, project.Avatar ?? string.Empty, project.Public));
    }

    [Authorize(Policy = "User")]
    [HttpGet("/projects")]
    public async Task<ActionResult<List<ProjectResponse>>> ListProjects(
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;
        var items = await db.Set<DocsProject>()
            .AsNoTracking()
            .Where(x => x.UserId == userId)
            .OrderBy(x => x.Name)
            .Select(x => new ProjectResponse(x.Id, x.Name, x.Description ?? string.Empty, x.Avatar ?? string.Empty, x.Public))
            .ToListAsync();

        return new OkObjectResult(items);
    }

    [Authorize(Policy = "User")]
    [HttpPost("/trees")]
    public async Task<ActionResult<TreeResponse>> CreateTree(
        [FromBody] CreateTreeRequest req,
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        if (req is null || req.ProjectId == Guid.Empty || string.IsNullOrWhiteSpace(req.Name))
        {
            return new BadRequestObjectResult(new { error = "InvalidRequest" });
        }

        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;

        var project = await db.Set<DocsProject>().FirstOrDefaultAsync(x => x.Id == req.ProjectId && x.UserId == userId);
        if (project == null)
        {
            return new BadRequestObjectResult(new { error = "ProjectNotFound" });
        }

        var tree = new DocsTree
        {
            Name = req.Name,
            Description = req.Description ?? string.Empty,
            UserId = userId,
            DocsProjectId = req.ProjectId
        };
        db.Set<DocsTree>().Add(tree);
        await db.SaveChangesAsync();

        var item = new DocsItem
        {
            DocsTreeId = tree.Id,
            Content = req.InitialContent ?? string.Empty,
            DocsProjectId = req.ProjectId
        };
        db.Set<DocsItem>().Add(item);
        await db.SaveChangesAsync();

        return new OkObjectResult(new TreeResponse(tree.Id, tree.Name, tree.Description ?? string.Empty, tree.DocsProjectId));
    }

    [Authorize(Policy = "User")]
    [HttpGet("/trees")]
    public async Task<ActionResult<List<TreeResponse>>> ListTrees(
        [FromQuery] Guid projectId,
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        if (projectId == Guid.Empty)
        {
            return new BadRequestObjectResult(new { error = "InvalidProjectId" });
        }

        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;

        // ensure project belongs to user
        var projectExists = await db.Set<DocsProject>()
            .AsNoTracking()
            .AnyAsync(x => x.Id == projectId && x.UserId == userId);
        if (!projectExists)
        {
            return new BadRequestObjectResult(new { error = "ProjectNotFound" });
        }

        var items = await db.Set<DocsTree>()
            .AsNoTracking()
            .Where(x => x.DocsProjectId == projectId && x.UserId == userId)
            .OrderBy(x => x.Name)
            .Select(x => new TreeResponse(x.Id, x.Name, x.Description ?? string.Empty, x.DocsProjectId))
            .ToListAsync();

        return new OkObjectResult(items);
    }

    [Authorize(Policy = "User")]
    [HttpGet("/items/{treeId}")]
    public async Task<ActionResult<ItemResponse>> GetItem(
        [FromRoute] string treeId,
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        if (string.IsNullOrWhiteSpace(treeId))
        {
            return new BadRequestObjectResult(new { error = "InvalidTreeId" });
        }

        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;

        var tree = await db.Set<DocsTree>()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == treeId && x.UserId == userId);
        if (tree == null)
        {
            return new NotFoundObjectResult(new { error = "TreeNotFound" });
        }

        var item = await db.Set<DocsItem>()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.DocsTreeId == treeId);
        if (item == null)
        {
            return new NotFoundObjectResult(new { error = "ItemNotFound" });
        }

        return new OkObjectResult(new ItemResponse(tree.Id, item.Content, item.LastModified, tree.DocsProjectId));
    }

    [Authorize(Policy = "User")]
    [HttpPut("/items/{treeId}")]
    public async Task<ActionResult<ItemResponse>> UpdateItem(
        [FromRoute] string treeId,
        [FromBody] UpdateItemRequest req,
        [FromServices] IContext context,
        [FromServices] IHttpContextAccessor accessor)
    {
        if (string.IsNullOrWhiteSpace(treeId) || req is null)
        {
            return new BadRequestObjectResult(new { error = "InvalidRequest" });
        }

        var principal = accessor.HttpContext?.User;
        var sub = principal?.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
                  ?? principal?.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(sub) || !Guid.TryParse(sub, out var userId))
        {
            return new UnauthorizedResult();
        }

        var db = (DbContext)context;

        var tree = await db.Set<DocsTree>()
            .FirstOrDefaultAsync(x => x.Id == treeId && x.UserId == userId);
        if (tree == null)
        {
            return new NotFoundObjectResult(new { error = "TreeNotFound" });
        }

        var item = await db.Set<DocsItem>()
            .FirstOrDefaultAsync(x => x.DocsTreeId == treeId);
        if (item == null)
        {
            return new NotFoundObjectResult(new { error = "ItemNotFound" });
        }

        item.Content = req.Content ?? string.Empty;

        await db.SaveChangesAsync();

        return new OkObjectResult(new ItemResponse(tree.Id, item.Content, item.LastModified, tree.DocsProjectId));
    }

    public record CreateProjectRequest(string Name, string? Description, string? Avatar, bool Public);
    public record ProjectResponse(Guid Id, string Name, string Description, string Avatar, bool Public);

    public record CreateTreeRequest(Guid ProjectId, string Name, string? Description, string? InitialContent);
    public record TreeResponse(string Id, string Name, string Description, Guid ProjectId);

    public record UpdateItemRequest(string? Content);
    public record ItemResponse(string TreeId, string Content, DateTime? LastModified, Guid ProjectId);
}